package org.davidtrevien.com.imput.omi.panels.center.planning._test;
 
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;

import javax.swing.AbstractAction;
import javax.swing.DropMode;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import javax.swing.TransferHandler;
import javax.swing.UIManager;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import org.jdesktop.swingx.JXTreeTable;
import org.jdesktop.swingx.treetable.DefaultTreeTableModel;
 
/**
 * Test class of the basics of JXTreeTable with the DefaultTreeTableModel and DefaultMutableTreeNodes.
 * Everything in one class to get a simple runnable example.
 * see https://swingx.dev.java.net/
 * @author jorgen.rapp
 *
 */
public class TestTreeTable {
	private JXTreeTable treeTable;
 
	public TestTreeTable() {
	}
 
	public static void main(String[] args) {
		try {
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		} catch (Exception e) {
			e.printStackTrace();
		}
		TestTreeTable test = new TestTreeTable();
		test.create();
	}
 
 
	/**
	 * creates the demo on the event dispatch thread.
	 *
	 */
	public void create() {
		Runnable r = new Runnable() {
			public void run() {
				JFrame f = new JFrame("Example of an editable JXTreeTable");
				f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
				JToolBar tb = new JToolBar();
				f.add(tb, BorderLayout.NORTH);
				tb.add(new InsertNodeAction());
				tb.add(new DeleteNodeAction());
 
				PersonTreeTableModel personTreeTableModel = generateTestModel();
				TestTreeTable.this.treeTable = new JXTreeTable(personTreeTableModel);
 
				TestTreeTable.this.treeTable.setTreeCellRenderer(new DefaultTreeCellRenderer() {
					@Override
					public java.awt.Component getTreeCellRendererComponent(
							javax.swing.JTree tree, Object value, boolean sel,
							boolean expanded, boolean leaf, int row,
							boolean hasFocus) {
						super.getTreeCellRendererComponent(tree, value, sel,
								expanded, leaf, row, hasFocus);
						if (value instanceof DefaultMutableTreeNode) {
							DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
							Person person = (Person) node
									.getUserObject();
							setText(person.getLastName());
							if (node.isLeaf()
									&& node.getParent() == tree.getModel()
											.getRoot()) {
								setIcon(getDefaultClosedIcon());
							}
						}
 
						return this;
					};
				});
 
				// -- NR: Drag'n Drop support
				TestTreeTable.this.treeTable.setDragEnabled(true);
				TestTreeTable.this.treeTable.setDropMode(DropMode.ON_OR_INSERT_ROWS);
				TestTreeTable.this.treeTable.setTransferHandler(new TransferHandler() {
					/* (non-Javadoc)
					 * @see javax.swing.TransferHandler#createTransferable(javax.swing.JComponent)
					 */
					@Override
					protected Transferable createTransferable(JComponent c) {
						Object node = TestTreeTable.this.treeTable.getPathForRow(TestTreeTable.this.treeTable.getSelectedRow()).getLastPathComponent();
						if (node instanceof DefaultMutableTreeNode) {
							DefaultMutableTreeNode dmtt = (DefaultMutableTreeNode) node;
							if (dmtt.getUserObject() instanceof Person) {
								return (Person) dmtt.getUserObject();
							}
						}
						return null;
					}
					/* (non-Javadoc)
					 * @see javax.swing.TransferHandler#getSourceActions(javax.swing.JComponent)
					 */
					@Override
					public int getSourceActions(JComponent c) {
						return COPY_OR_MOVE;
					}
				});
				// -- !NR
 
				f.add(new JScrollPane(TestTreeTable.this.treeTable));
				f.setSize(300, 300);
				f.setVisible(true);
			}
		};
		EventQueue.invokeLater(r);
	}
 
 
	/**
	 * Inserts a new node, the kind and name depends on the selected node.
	 *
	 */
	class InsertNodeAction extends AbstractAction {
		InsertNodeAction() {
			super("Insert");
		}
 
		public void actionPerformed(ActionEvent e) {
			TreePath selp = TestTreeTable.this.treeTable.getTreeSelectionModel().getSelectionPath();
			DefaultMutableTreeNode insertAtNode;
			String famName = null;
			boolean isFamilyNode = false;
			if (selp == null) {
				//nothing selected, create a new family
				insertAtNode = (DefaultMutableTreeNode) TestTreeTable.this.treeTable
						.getTreeTableModel().getRoot();
				famName = JOptionPane.showInputDialog(SwingUtilities.windowForComponent(TestTreeTable.this.treeTable),"What's the new Family name?");
				isFamilyNode = true;
			} else {
				insertAtNode = (DefaultMutableTreeNode) selp
						.getLastPathComponent();
				// pick the closest family node, in this case adding a child  
				// to a PersonNode doesn't make sense.
				if (insertAtNode.isLeaf()
						&& !(insertAtNode.getParent() == TestTreeTable.this.treeTable
								.getTreeTableModel().getRoot())) {
					insertAtNode = (DefaultMutableTreeNode) insertAtNode
							.getParent();
					isFamilyNode = false;
				}
				famName = ((Person) insertAtNode.getUserObject()).getLastName();
			}
			Person nuObj = generateTestPerson(famName, isFamilyNode);
			DefaultMutableTreeNode nuNode = new DefaultMutableTreeNode(nuObj);
			insertAtNode.add(nuNode);
			int idx = insertAtNode.getIndex(nuNode);
 
			int[] childIndices = new int[1];
			childIndices[0] = idx;
			((DefaultTreeTableModel) TestTreeTable.this.treeTable.getTreeTableModel())
					.nodesWereInserted(insertAtNode, childIndices);
			TestTreeTable.this.treeTable.expandPath(new TreePath(insertAtNode.getPath()));
			TestTreeTable.this.treeTable.getTreeSelectionModel().setSelectionPath(new TreePath(nuNode.getPath()));
		}
	}
 
	/**
	 * Deletes a node after one is selected.
	 *
	 */
	class DeleteNodeAction extends AbstractAction {
		DeleteNodeAction() {
			super("Delete");
		}
 
		public void actionPerformed(ActionEvent e) {
			TreePath selp = TestTreeTable.this.treeTable.getTreeSelectionModel().getSelectionPath();
			if (selp == null) {
				JOptionPane.showMessageDialog(TestTreeTable.this.treeTable,
						"Must select a node in the tree before you can delete", "Error",
						JOptionPane.ERROR_MESSAGE);
			} else {
				DefaultMutableTreeNode toBeDeletedNode = (DefaultMutableTreeNode) selp
						.getLastPathComponent();
				// find out which node to select next
				DefaultMutableTreeNode nextToBeSelected = toBeDeletedNode
						.getNextSibling();
				if (nextToBeSelected == null) {
					nextToBeSelected = toBeDeletedNode.getPreviousSibling();
					if (nextToBeSelected == null) {
						nextToBeSelected = (DefaultMutableTreeNode) toBeDeletedNode
								.getParent();
					}
				}
				DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) toBeDeletedNode
						.getParent();
				int idx = parentNode.getIndex(toBeDeletedNode);
				parentNode.remove(toBeDeletedNode);
 
				int[] childIndices = new int[1];
				childIndices[0] = idx;
				Object[] delNodes = new Object[1];
				delNodes[0] = toBeDeletedNode;
				((DefaultTreeTableModel) TestTreeTable.this.treeTable.getTreeTableModel())
						.nodesWereRemoved(parentNode, childIndices, delNodes);
				TestTreeTable.this.treeTable.getTreeSelectionModel().setSelectionPath(
						new TreePath(nextToBeSelected.getPath()));
			}
		}
	}
 
	/**
	 * The TreeTableModelshould be top level class really.
	 * 
	 * @author jorgen.rapp
	 * 
	 */
	class PersonTreeTableModel extends DefaultTreeTableModel {
		private static final int LAST = 0;
 
		private static final int FIRST = 1;
 
		private static final int MIDDLE = 2;
 
		private static final int PHONE = 3;
 
		public PersonTreeTableModel(TreeNode node) {
			super(node);
		}
 
		@Override
		public int getColumnCount() {
			return 4;
		}
 
		/**
		 * Returns which object is displayed in this column.
		 */
		@Override
		public Object getValueAt(Object node, int column) {
			Object res = "n/a";
			if (node instanceof DefaultMutableTreeNode) {
				DefaultMutableTreeNode defNode = (DefaultMutableTreeNode) node;
				if (defNode.getUserObject() instanceof Person) {
					Person person = (Person) defNode.getUserObject();
					switch (column) {
					case FIRST:
						res = person.getFirstName();
						break;
					case MIDDLE:
						res = person.getMiddleName();
						break;
					case LAST:
						res = person.getLastName();
						break;
					case PHONE:
						res = person.getPhoneNbr();
						break;
					}
				}
			}
			return res;
		}
 
		/**
		 * What the TableHeader displays when the Table is in a JScrollPane.
		 */
		@Override
		public String getColumnName(int column) {
			String res = "";
			switch (column) {
			case FIRST:
				res = "First";
				break;
			case MIDDLE:
				res = "Middle";
				break;
			case LAST:
				res = "Last";
				break;
			case PHONE:
				res = "Phone";
				break;
			}
			return res;
		}
 
 
		/**
		 * Tells if a column can be edited.
		 */
		@Override
		public boolean isCellEditable(Object node, int column) {
			return true;
		}
 
		/**
		 * Called when done editing a cell.
		 */
		@Override
		public void setValueAt(Object value, Object node, int column) {
			if (node instanceof DefaultMutableTreeNode) {
				DefaultMutableTreeNode defNode = (DefaultMutableTreeNode) node;
				if (defNode.getUserObject() instanceof Person) {
					Person person = (Person) defNode.getUserObject();
					switch (column) {
					case FIRST:
						person.setFirstName(value.toString());
						break;
					case MIDDLE:
						person.setMiddleName(value.toString());
						break;
					case LAST:
						person.setLastName(value.toString());
						break;
					case PHONE:
						person.setPhoneNbr(value.toString());
						break;
					}
				}
			}
		}
	}
 
	/**
	 * Generates a PersonTreeTableModel of fake persons.
	 * @return
	 */
	public PersonTreeTableModel generateTestModel() {
		Set<Person> list = new TreeSet<Person>();
		// gen test persons
		for (int i = 0; i < 20; i++) {
			list.add(generateTestPerson(null, false));
		}
		String prevLast = "";
		String currentLast = "";
		DefaultMutableTreeNode currentNameNode = null;
		// shouldn't be visible
		DefaultMutableTreeNode aRoot = new DefaultMutableTreeNode(
				new Person());
		for (Person testPerson : list) {
			currentLast = testPerson.getLastName();
			if (currentLast.equals(prevLast)) {
				currentNameNode.add(new DefaultMutableTreeNode(testPerson));
			} else {
				if (currentNameNode != null) {
					aRoot.add(currentNameNode);
				}
				currentNameNode = new DefaultMutableTreeNode(new Person("",
						"", testPerson.getLastName(), ""));
				currentNameNode.add(new DefaultMutableTreeNode(testPerson));
				prevLast = currentLast;
			}
		}
		return new PersonTreeTableModel(aRoot);
	}
 
	private static String[] FIRST_NAMES = { "Amy", "Bob", "Cedric", "Dan",
			"Erica", "Fred" };
 
	private static String[] LAST_NAMES = { "Anders", "Blagg", "Caesar",
			"Dickens", "Evans", "Foo" };
 
	/**
	 * @return a String of U.S. phone format (nnn)nnn-nnnn
	 */
	private static String genPhone() {
		Random rnd = new Random();
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < 10; i++) {
			sb.append(rnd.nextInt(10));
		}
		sb.insert(6, '-');
		sb.insert(3, ')');
		sb.insert(0, '(');
		return sb.toString();
	}
 
	private static Random rnd = new Random();
 
	/**
	 * 
	 * @param familyName if null it will be generated
	 * @param isFamilyNode if true don't generate first, middle, phone. 
	 * @return a Person object
	 */
	public/* static */Person generateTestPerson(String familyName,
			boolean isFamilyNode) {
		int firstN = rnd.nextInt(FIRST_NAMES.length);
		int middleN = rnd.nextInt(FIRST_NAMES.length);
		int lastN = rnd.nextInt(LAST_NAMES.length);
		if (isFamilyNode) {
			return new Person("","", 
					(familyName != null ? familyName: LAST_NAMES[lastN]), "");
		} else {
			return new Person(FIRST_NAMES[firstN], FIRST_NAMES[middleN]
					.substring(0, 1), (familyName != null ? familyName
					: LAST_NAMES[lastN]), genPhone());
		}
	}
 
	/**
	 * The userObject of the node. 
	 * Top level class.
	 * 
	 * NR: Add transferable
	 */
	class Person implements Comparable<Person>, Transferable {
		private String firstName = "";
		private String middleName = "";
		private String lastName = "";
		private String phoneNbr = "";
 
		public Person() {
		}
 
		public Person(String firstName, String middleName, String lastName,
				String phoneNbr) {
			super();
			this.firstName = firstName;
			this.middleName = middleName;
			this.lastName = lastName;
			this.phoneNbr = phoneNbr;
		}
 
		public String getFirstName() {
			return this.firstName;
		}
 
		public void setFirstName(String firstName) {
			this.firstName = firstName;
		}
 
		public String getLastName() {
			return this.lastName;
		}
 
		public void setLastName(String lastName) {
			this.lastName = lastName;
		}
 
		public String getMiddleName() {
			return this.middleName;
		}
 
		public void setMiddleName(String middleName) {
			this.middleName = middleName;
		}
 
		public String getPhoneNbr() {
			return this.phoneNbr;
		}
 
		public void setPhoneNbr(String phoneNbr) {
			this.phoneNbr = phoneNbr;
		}
 
		@Override
		public String toString() {
			return this.firstName + " " + this.middleName + ". " + this.lastName + "\t"
					+ this.phoneNbr;
		}
 
		@Override
		public int hashCode() {
			final int PRIME = 31;
			int result = 1;
			result = PRIME * result
					+ ((this.firstName == null) ? 0 : this.firstName.hashCode());
			result = PRIME * result
					+ ((this.middleName == null) ? 0 : this.middleName.hashCode());
			result = PRIME * result
					+ ((this.lastName == null) ? 0 : this.lastName.hashCode());
			result = PRIME * result
					+ ((this.phoneNbr == null) ? 0 : this.phoneNbr.hashCode());
			return result;
		}
 
		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			final Person other = (Person) obj;
			if (this.firstName == null) {
				if (other.firstName != null)
					return false;
			} else if (!this.firstName.equals(other.firstName))
				return false;
			if (this.middleName == null) {
				if (other.middleName != null)
					return false;
			} else if (!this.middleName.equals(other.middleName))
				return false;
			if (this.lastName == null) {
				if (other.lastName != null)
					return false;
			} else if (!this.lastName.equals(other.lastName))
				return false;
			if (this.phoneNbr == null) {
				if (other.phoneNbr != null)
					return false;
			} else if (!this.phoneNbr.equals(other.phoneNbr))
				return false;
			return true;
		}
 
		public int compareTo(Person otherObject) {
			int res = 0;
			res = otherObject.getLastName().compareTo(getLastName());
			if (0 == res) {
				res = otherObject.getFirstName().compareTo(getFirstName());
			}
			if (0 == res) {
				res = otherObject.getMiddleName().compareTo(getMiddleName());
			}
			return res;
		}
 
		/* (non-Javadoc)
		 * @see java.awt.datatransfer.Transferable#getTransferData(java.awt.datatransfer.DataFlavor)
		 */
		public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
			if (isDataFlavorSupported(flavor)) {
				return this;
			}
			throw new UnsupportedFlavorException(flavor);
		}
 
		/* (non-Javadoc)
		 * @see java.awt.datatransfer.Transferable#getTransferDataFlavors()
		 */
		public DataFlavor[] getTransferDataFlavors() {
			return new DataFlavor[] { new DataFlavor(Person.class, "Person") };
		}
 
		/* (non-Javadoc)
		 * @see java.awt.datatransfer.Transferable#isDataFlavorSupported(java.awt.datatransfer.DataFlavor)
		 */
		public boolean isDataFlavorSupported(DataFlavor flavor) {
			return flavor.equals(new DataFlavor(Person.class, "Person"));
		}
	}
 
}